/**
 * 
 */
package cn.edu.cqu.bysj.wm.neo4j;

import java.util.ArrayList;
import java.util.List;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.traversal.Evaluators;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.kernel.Traversal;
import org.neo4j.kernel.Uniqueness;

import cn.edu.cqu.bysj.wm.def.PropertyDef;
import cn.edu.cqu.bysj.wm.def.RelTypeDef;

/**
 * @author Meng
 * 
 */
public class Neo4jTraversal {

	// used in judge connected graph of gn
	private final static TraversalDescription CONNECT_TRAVERSAL = Traversal
			.description().breadthFirst().relationships(RelTypeDef.LOCK)
			.relationships(RelTypeDef.EDGE, Direction.BOTH)
			.uniqueness(Uniqueness.NODE_GLOBAL);

	// depth fist traversal from htree root to...
	private final static TraversalDescription TREE_TRAVERSAL = Traversal
			.description().depthFirst()
			.relationships(RelTypeDef.PARENT_OF, Direction.OUTGOING);

	// used in calculate the connected nodes or degree of a node
	// used in calculate the cluster coefficient of a node
	// used in judge the strength of community
	private final static TraversalDescription FRIENDS_TRAVERSAL = Traversal
			.description().breadthFirst();

	/**
	 * traverse to get the number of relationship connected with nodes inside
	 * community used in judge the week strength of community
	 * 
	 * @param nodes
	 * @param direction
	 * @return number of relationships
	 */
	public static int getRelationshipsInCommunity(List<Node> nodes) {

		int num = 0;
		for (Relationship rel : FRIENDS_TRAVERSAL
				.uniqueness(Uniqueness.RELATIONSHIP_GLOBAL)
				.relationships(RelTypeDef.EDGE).relationships(RelTypeDef.LOCK)
				.relationships(RelTypeDef.DELETE).traverse(nodes.get(0))
				.relationships()) {
			if (nodes.contains(rel.getStartNode())
					&& nodes.contains(rel.getEndNode())) {
				int w = (Integer) rel.getProperty(PropertyDef.WEIGHT);
				num += w;
			}
		}
		return num;
	}

	/**
	 * traverse to get the number of relationship connected to node inside
	 * community used in judge the strong strength of community
	 * 
	 * @param node
	 * @param nodes
	 * @return number of relationships
	 */
	public static int getRelationshipsInCommunity(Node node, List<Node> nodes) {

		int num = 0;
		for (Relationship rel : FRIENDS_TRAVERSAL
				.uniqueness(Uniqueness.RELATIONSHIP_GLOBAL)
				.relationships(RelTypeDef.EDGE).relationships(RelTypeDef.LOCK)
				.relationships(RelTypeDef.DELETE)
				.evaluator(Evaluators.toDepth(1)).traverse(node)
				.relationships()) {
			if (nodes.contains(rel.getStartNode())
					&& nodes.contains(rel.getEndNode())) {
				int w = (Integer) rel.getProperty(PropertyDef.WEIGHT);
				num += w;
			}
		}
		return num;
	}

	/**
	 * traverse to get the number of relationship connected with nodes outside
	 * community used in judge the week strength of community
	 * 
	 * @param nodes
	 * @param direction
	 * @return number of relationships
	 */
	public static int getRelationshipsOutCommunity(List<Node> nodes,
			Direction direction) {
		int num = 0;
		for (Relationship rel : FRIENDS_TRAVERSAL
				.uniqueness(Uniqueness.RELATIONSHIP_GLOBAL)
				.relationships(RelTypeDef.EDGE).relationships(RelTypeDef.LOCK)
				.relationships(RelTypeDef.DELETE).traverse(nodes.get(0))
				.relationships()) {
			if (direction.equals(Direction.BOTH)) {
				if (nodes.contains(rel.getStartNode())
						&& !nodes.contains(rel.getEndNode())
						|| !nodes.contains(rel.getStartNode())
						&& nodes.contains(rel.getEndNode())) {
					num++;
				}
			} else if (direction.equals(Direction.OUTGOING)) {
				if (nodes.contains(rel.getStartNode())
						&& !nodes.contains(rel.getEndNode())) {
					num++;
				}
			} else if (direction.equals(Direction.INCOMING)) {
				if (nodes.contains(rel.getEndNode())
						&& !nodes.contains(rel.getStartNode())) {
					num++;
				}
			}
		}
		return num;
	}

	/**
	 * traverse to get the number of relationship connected to node outside
	 * community used in judge the strong strength of community
	 * 
	 * @param node
	 * @param nodes
	 * @param direction
	 * @return number of relationships
	 */
	public static int getRelationshipsOutCommunity(Node node, List<Node> nodes,
			Direction direction) {
		int num = 0;
		for (Relationship rel : FRIENDS_TRAVERSAL
				.uniqueness(Uniqueness.RELATIONSHIP_GLOBAL)
				.relationships(RelTypeDef.EDGE).relationships(RelTypeDef.LOCK)
				.relationships(RelTypeDef.DELETE)
				.evaluator(Evaluators.toDepth(1)).traverse(node)
				.relationships()) {
			if (direction.equals(Direction.BOTH)) {
				if (nodes.contains(rel.getStartNode())
						&& !nodes.contains(rel.getEndNode())
						|| !nodes.contains(rel.getStartNode())
						&& nodes.contains(rel.getEndNode())) {
					num++;
				}
			} else if (direction.equals(Direction.OUTGOING)) {
				if (nodes.contains(rel.getStartNode())
						&& !nodes.contains(rel.getEndNode())) {
					num++;
				}
			} else if (direction.equals(Direction.INCOMING)) {
				if (nodes.contains(rel.getEndNode())
						&& !nodes.contains(rel.getStartNode())) {
					num++;
				}
			}
		}
		return num;
	}

	/**
	 * traverse to get the connected nodes of a node
	 * 
	 * @param sNode
	 * @param direction
	 * @return nodes
	 */
	public static List<Node> getAdjoinedNodes(Node sNode, Direction direction) {
		List<Node> list = new ArrayList<Node>();
		for (Node node : FRIENDS_TRAVERSAL.uniqueness(Uniqueness.NODE_GLOBAL)
				.evaluator(Evaluators.toDepth(1))
				.relationships(RelTypeDef.EDGE, direction)
				.relationships(RelTypeDef.LOCK, direction)
				.relationships(RelTypeDef.DELETE, direction).traverse(sNode)
				.nodes()) {
			list.add(node);
		}
		list.remove(sNode);
		return list;
	}

	/**
	 * traverse to get the connected nodes in the same community of a node
	 * 
	 * @param sNode
	 * @param nodes
	 * @param direction
	 * @return nodes
	 */
	public static List<Node> getAdjoinedNodes(Node sNode, List<Node> nodes,
			Direction direction) {
		List<Node> list = new ArrayList<Node>();
		for (Node node : FRIENDS_TRAVERSAL.uniqueness(Uniqueness.NODE_GLOBAL)
				.evaluator(Evaluators.toDepth(1))
				.relationships(RelTypeDef.EDGE, direction)
				.relationships(RelTypeDef.LOCK, direction)
				.relationships(RelTypeDef.DELETE, direction).traverse(sNode)
				.nodes()) {
			if (nodes.contains(node)) {
				list.add(node);
			}
		}
		list.remove(sNode);
		return list;
	}

	/**
	 * traverse to get the exist relationships of nodes connected to the center
	 * node used in calculate the cluster coefficient of a node
	 * 
	 * @param sNode
	 * @param direction
	 * @return relationships
	 */
	public static List<Relationship> getEdgesInAdjoinedNodes(Node sNode,
			List<Node> nList) {
		List<Relationship> list = new ArrayList<Relationship>();
		for (Relationship rel : FRIENDS_TRAVERSAL
				.uniqueness(Uniqueness.RELATIONSHIP_GLOBAL)
				.evaluator(Evaluators.toDepth(2))
				.relationships(RelTypeDef.EDGE).relationships(RelTypeDef.LOCK)
				.relationships(RelTypeDef.DELETE).traverse(sNode)
				.relationships()) {
			if (nList.contains(rel.getStartNode())
					&& nList.contains(rel.getEndNode())) {
				list.add(rel);
			}
		}
		return list;
	}

	/**
	 * traverse to get the nodes of a connect graph from sNode
	 * 
	 * @param sNode
	 *            start node
	 * @return nodes
	 */
	public static List<Node> connectGraphNodes(Node sNode) {
		List<Node> nList = new ArrayList<Node>();
		for (Node node : CONNECT_TRAVERSAL.traverse(sNode).nodes()) {
			nList.add(node);
		}
		return nList;
	}

	/**
	 * traverse to get the relationships of a connect graph from sNode
	 * 
	 * @param sNode
	 *            start node
	 * @return relationships
	 */
	public static List<Relationship> connectGraphEdges(Node sNode) {
		List<Relationship> rList = new ArrayList<Relationship>();
		for (Relationship rel : CONNECT_TRAVERSAL.traverse(sNode)
				.relationships()) {
			rList.add(rel);
		}
		return rList;
	}

	/**
	 * traverse to get the depth of htree
	 * 
	 * @param root
	 *            root node
	 * @return depth
	 */
	public static int getTreeDepth(Node root) {
		int depth = 0;
		for (Path path : TREE_TRAVERSAL.uniqueness(Uniqueness.NODE_PATH)
				.traverse(root)) {
			int len = path.length();
			depth = depth > len ? depth : len;
		}
		return depth;
	}

	/**
	 * traverse to get leaf nodes of a depth
	 * 
	 * @param root
	 *            root node
	 * @param depth
	 *            depth to traversal
	 * @return leaf nodes
	 */
	public static List<Node> getLeafNodesByDepth(Node root, int depth) {
		List<Node> list = new ArrayList<Node>();
		for (Node node : TREE_TRAVERSAL.uniqueness(Uniqueness.NODE_GLOBAL)
				.evaluator(Evaluators.toDepth(depth)).traverse(root).nodes()) {
			if (((Integer) node.getProperty(PropertyDef.LEVEL)).equals(depth)
					|| !node.getRelationships(Direction.OUTGOING).iterator()
							.hasNext())
				list.add(node);
		}
		return list;
	}
}
